/**
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *    Copyright (c) 2003-2006 TU/e Eindhoven
 *    by Eindhoven University of Technology
 *    Department of Information Systems
 *    http://is.tm.tue.nl
 *
 ************************************************************************/

package org.processmining.analysis.decisionmining;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.processmining.framework.log.LogEvent;
import org.processmining.framework.models.hlprocess.HLActivity;
import org.processmining.framework.models.hlprocess.HLAttribute;
import org.processmining.framework.models.hlprocess.HLProcess;
import org.processmining.framework.models.hlprocess.HLTypes.AttributeType;
import org.processmining.framework.models.hlprocess.att.HLAttributeManager;
import org.processmining.framework.models.hlprocess.att.HLNominalAttribute;
import org.processmining.framework.models.hlprocess.att.HLNumericAttribute;
import org.processmining.framework.models.hlprocess.hlmodel.HLPetriNet;
import org.processmining.framework.models.petrinet.PetriNet;
import org.processmining.framework.models.petrinet.Transition;
import org.processmining.framework.util.GUIPropertyListWithoutGlue;
import org.processmining.framework.util.GuiNotificationTarget;

import weka.core.Attribute;
import weka.core.FastVector;

/**
 * Represents a case attribute as it has been observed in the log. <br>
 * Maintains the corresponding GUI panel where the attribute may be included or
 * excluded from analysis, and the proper attribute type may be chosen (such as
 * numeric or nominal). <br>
 * Furthermore it creates a link to the data mining application (see
 * #getWekaAttribute getWekaAttribute).
 * 
 * @author arozinat
 */
public class DecisionAttributeForAuLdg implements GuiNotificationTarget {

  /** numeric value statistics (may be empty) */
 // private SummaryStatistics myNumericValues = SummaryStatistics.newInstance();
  /** which tasks have written this attribute */
  private HashSet<LogEvent> myLogEvents = new HashSet<LogEvent>();

  private HLPetriNet hlPetriNet;
  /**
   * simulation model attributes (need to be updated as soon as attribute is,
   * e.g., deselected)
   */
  private HLProcess hlProcess;
  /** the high level attribute linked to this decision attribute */
  protected HLAttribute hlAttribute;
  protected HLNominalAttribute nominalBackup;

  private JPanel myPanel;
  private JCheckBox myNameCheckBox;
  private GUIPropertyListWithoutGlue myTypeGuiRepresenation;

  /**
   * Default constructor.
   * 
   * @param name
   *          the name of the attribute
   * @param values
   *          the set of values observed in the log
   */
  public DecisionAttributeForAuLdg(String name, HLPetriNet highLevelPN) {
    hlAttribute = new HLNominalAttribute(name, highLevelPN.getHLProcess());
    hlPetriNet = highLevelPN;
    hlProcess = highLevelPN.getHLProcess();
  }

  /**
   * Returns the name of this attribute.
   * 
   * @return the name of this attribute
   */
  public String getName() {
    return hlAttribute.getName();
  }

  /**
   * Adds the passed value to the values already held for this attribute.
   * Duplicate values will not be added twice, so the collection of values
   * corresponds to a set semantics.
   * 
   * @param newValues
   *          the new value to be added
   */
  public void addValue(String newValue) {
    // add value to enumerated values
    if (newValue != null) {
      ((HLNominalAttribute) hlAttribute).addPossibleValue(newValue);
      // if is numeric also add value to statistics
      try {
        long numericValue = Long.parseLong(newValue);
        //myNumericValues.addValue(numericValue);
      } catch (NumberFormatException ex) {
        // do nothing as value is simply not numeric
      }
    }
  }

  /**
   * Adds the given log event to the list of log events held for this attribute.
   * This is to keep track on which tasks have written this attribute.
   * 
   * @param logEvent
   *          the log event to be added
   */
  public void addLogEvent(LogEvent logEvent) {
    if (logEvent != null) {
      myLogEvents.add(logEvent);
    }
  }

  /**
   * Determines whether this attribute was attached to audit trail entries which
   * are associated to the given log event.
   * 
   * @param logEvent
   *          the log event to be checked
   * @return <code>true</code> if this attribute was observed for at least one
   *         audit trail entry associated to the given log event,
   *         <code>false</code> otherwise
   */
  public boolean hasBeenObservedBy(LogEvent logEvent) {
    if (myLogEvents.contains(logEvent) == true) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * Retrieves the set of log events that have provided this attribute.
   * 
   * @return the set of log events that specified this attribute in the given
   *         log
   */
  public Set<LogEvent> getLogEvents() {
    return myLogEvents;
  }

  /**
   * Retrieves this attribute as a weka attribute (regardless whether it was
   * selected by the user or not).
   * 
   * @return the weka attribute corresponding to this attribute
   */
  public Attribute getWekaAttribute() {
    DecisionAttributeTypeForAuLdg myType = getAttributeType();
    // if no type has been chosen yet
    if (myType == null) {
      return null;
    }
    Attribute result = null;
    // build nominal attribute
    if (myType.equals(DecisionAttributeTypeForAuLdg.NOMINAL)) {
      List<String> values = ((HLNominalAttribute) hlAttribute)
          .getPossibleValues().getValues();
      FastVector my_nominal_values = new FastVector(values.size());
      for (String attValue : values) {
        my_nominal_values.addElement(attValue);
      }
      // add vector to attribute
      result = new Attribute(hlAttribute.getName(), my_nominal_values);
    } else if (myType.equals(DecisionAttributeTypeForAuLdg.NUMERIC)) {
      // make attribute a numeric one
      result = new Attribute(hlAttribute.getName());
    }
    return result;
  }

  /**
   * Creates (and adds) a new data attribute object in the simulation model. <br>
   * Adds the given simulation attribute to all high level activities in the
   * associated simulation model that have provided this attribute at least once
   * in the log.
   * 
   * @return the new high level data attribute for this decision attribute
   */
  public void createSimulationAttribute() {
    AttributeType type = getAttributeType().getHighLevelAttributeType();
    // create nominal high level attribute
    if (type == AttributeType.Nominal
        && hlAttribute instanceof HLNumericAttribute) {
      ArrayList<String> valList = new ArrayList<String>();
      Iterator it = nominalBackup.getPossibleValues().getValues().iterator();
      while (it.hasNext()) {
        String val = (String) it.next();
        int freq = nominalBackup.getPossibleValues()
            .getFrequencyPossibleValueNominal(val);
        for (int i = 0; i < freq; i++) {
          valList.add(val);
        }
      }
      // initial value is the first value in list (can be changed before export)
      HLAttribute previousAtt = hlAttribute;
      hlAttribute = new HLNominalAttribute(getName(), valList, hlProcess);
      hlProcess.replaceAttribute(previousAtt.getID(), hlAttribute);
    }
    // create numeric high level attribute
    else if (type == AttributeType.Numeric
        && hlAttribute instanceof HLNominalAttribute) {
      nominalBackup = (HLNominalAttribute) hlAttribute;
      hlAttribute = HLAttributeManager
          .autoChangeType((HLNominalAttribute) hlAttribute);
    }
    // add the same attribute to all activities (in the simulation model)
    // that have written the attribute
    Iterator<LogEvent> logEventIt = getLogEvents().iterator();
    while (logEventIt.hasNext()) {
      LogEvent le = logEventIt.next();
      // find associated transitions in the process model
      PetriNet pn = (PetriNet) hlPetriNet.getProcessModel();
      Iterator transIt = pn.findTransitions(le).iterator();
      while (transIt.hasNext()) {
        Transition trans = (Transition) transIt.next();
        HLActivity hlTrans = hlPetriNet.findActivity(trans);
        hlTrans.addOutputDataAttribute(hlAttribute.getID());
      }
    }
  }

  /**
   * Returns currently held simulation attribute. Note that as soon as the type
   * of the attribute changes, a new simulation attribute object will be created
   * and assigned to this decision attribute.
   * 
   * @return the current simulation attribute object
   */
  public HLAttribute getSimulationAttribute() {
    return hlAttribute;
  }

  /**
   * Retrieves the corresponding GUI panel for this attribute. If it has not
   * been built yet, it will be built now.
   * 
   * @return the panel belonging to this attribute
   */
  public JPanel getAttributePanel() {
    if (myPanel == null) {
      buildAttributePanel();
    }
    return myPanel;
  }

  /**
   * Determines the selection state of the belonging check box item (i.e.,
   * whether the user has or has not chosen the respective attribute to be
   * included into analysis).
   */
  public boolean isIncluded() {
    boolean result = false;
    if (myNameCheckBox != null) {
      result = myNameCheckBox.isSelected();
    }
    return result;
  }

  /**
   * Overridden to specify when two DecisionAttribute objects are considered to
   * be equal.
   * 
   * @param o
   *          the <code>DecisionAttribute</code> to be compared with
   * @return <code>true</code> if the identifiers are the same,
   *         <code>false</code> otherwise.
   */
  public boolean equals(Object o) {
    // check object identity first
    if (this == o) {
      return true;
    }
    // check type (which includes check for null)
    return (o instanceof DecisionAttributeForAuLdg)
        && hlAttribute.getID().equals(
            ((DecisionAttributeForAuLdg) o).hlAttribute.getID());
  }

  /**
   * Overridden to produce the same hash code for equal objects.
   * 
   * @return the hash code calculated
   */
  public int hashCode() {
    // simple recipe for generating hashCode given by
    // Effective Java (Addison-Wesley, 2001)
    int result = 17;
    result = 37 * result + hlAttribute.getID().hashCode();
    return result;
  }

  /*
   * This method is called as soon as the attribute type changes.
   * 
   * (non-Javadoc)
   * 
   * @see org.processmining.framework.util.GuiNotificationTarget#updateGUI()
   */
  public void updateGUI() {
    // remove old simulation attribute
    // (automatically removes attribute from contained activities)
    HLAttribute oldAtt = getSimulationAttribute();
    hlProcess.removeAttribute(oldAtt.getID());
    // create new simulation attribute (with new attribute type)
    // and add to related activities
    createSimulationAttribute();
  }

  // //////////////////// Private methods
  // //////////////////////////////////////////////

  /**
   * Retrieves the current type of this attribute (i.e., numeric or nominal).
   * 
   * @return the attribute type if the graphical representation has been built
   *         already, <code>null</code> otherwise
   */
  protected DecisionAttributeTypeForAuLdg getAttributeType() {
    DecisionAttributeTypeForAuLdg result = null;
    if (myTypeGuiRepresenation != null) {
      result = (DecisionAttributeTypeForAuLdg) myTypeGuiRepresenation.getValue();
    } else {
      result = DecisionAttributeTypeForAuLdg.NOMINAL;
    }
    return result;
  }

  /**
   * Sets the specified attribut type if possible.
   * 
   * @param type
   *          the new type
   */
  protected void setDecisionAttributeType(DecisionAttributeTypeForAuLdg type) {
    if (myTypeGuiRepresenation == null) {
      createAttributeTypeGui();
    }
    myTypeGuiRepresenation.setValue(type);
  }

  /**
   * Builds a panel containing a checkbox to include or exclude this attribute,
   * and a combobox in order to chose an attribute type.
   */
  private void buildAttributePanel() {
    myPanel = new JPanel();
    myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.LINE_AXIS));
    JLabel myNameLabel = new JLabel("Attribute name: ");
    myNameLabel.setForeground(new Color(100, 100, 100));
    myPanel.add(myNameLabel);
    myNameCheckBox = new JCheckBox(getName());
    // per default select everything
    myNameCheckBox.setSelected(true);

    // register check/uncheck action
    myNameCheckBox.addActionListener(new ActionListener() {
      // specify action when the attribute selection scope is changed
      public void actionPerformed(ActionEvent e) {
        JCheckBox cb = (JCheckBox) e.getSource();
        HLAttribute simAtt = getSimulationAttribute();
        if (cb.isSelected() == true) {
          myTypeGuiRepresenation.enable();
          // add attribute to simulation model
          // and to related activities
          createSimulationAttribute();
        } else {
          myTypeGuiRepresenation.disable();
          // remove attribute from simulation model
          // (automatically removes attribute from contained activities)
          hlProcess.removeAttribute(simAtt.getID());
        }
      }
    });

    myPanel.add(myNameCheckBox);
    myPanel.add(Box.createHorizontalGlue());

    createAttributeTypeGui();
    // ArrayList<DecisionAttributeType> attributeTypes = new
    // ArrayList<DecisionAttributeType>();
    // // per default set "nominal"
    // attributeTypes.add(DecisionAttributeType.NOMINAL);
    // attributeTypes.add(DecisionAttributeType.NUMERIC);
    // myTypeGuiRepresenation = new
    // GUIPropertyListWithoutGlue("Attribute type:",
    // "Please determine the type of the attribute", attributeTypes, this);

    myPanel.add(myTypeGuiRepresenation.getPropertyPanel());
  }

  private void createAttributeTypeGui() {
    ArrayList<DecisionAttributeTypeForAuLdg> attributeTypes = new ArrayList<DecisionAttributeTypeForAuLdg>();
    // per default set "nominal"
    attributeTypes.add(DecisionAttributeTypeForAuLdg.NOMINAL);
    attributeTypes.add(DecisionAttributeTypeForAuLdg.NUMERIC);
    myTypeGuiRepresenation = new GUIPropertyListWithoutGlue("Attribute type:",
        "Please determine the type of the attribute", attributeTypes, this);
  }

}
